﻿/*
A DISCUSSION ON RESETS

Various container types and interactions will make a container "resettable."
What this effectively means is that these containers then become event
listeners.

The issue is, what do they listen to? There are two scenarios: either the
container is managed or has an ancestor that is managed, or it is not. In the
former case, the container will listen to itself or to its ancestor for the
reset events. In the latter case, it will listen to the document.

Listening to the document is troublesome because of the need to get an initial 
reset. The problem is that if we've added a document segment to an already 
existent document, we only want to reset the newly added portion. To solve this, 
we have two reset signals: the initial reset and the plain reset. 

When a resettable container is added to a document and isn't managed, we add a 
listener to the document for the initial reset event. When it gets this event 
the first time, we remove the listener. We also add a listener to the document 
for the plain reset event and this one persists.

Note that in this scenario, the only containers to ever get the initial reset 
event are containers that are not managed. This should not be a problem because 
we expect the manager (a resettable container) to dispatch a reset event 
whenever it is itself reset or when it is told to select a new child and the 
appropriate styles are set.

Also note that both the initial reset and the plain reset call the reset method
on the container. The reset function cannot distinguish between the two calls
though it may use the isInitialReset member variable to determine if this is the
first reset call or not (not a necessarily equivalent to the initial reset which
may occur after a manual call to reset for example).

XXX There is a degenerate case to be handled which is that of adding a
resettable container to a managed and selected container. In this case, it is
added after the reset event it would have been listening to has been sent. We
need to handle this as a special case.

Resettable containers include: managers, video containers, image containers 
(containing movieclips or external SWFs), any container with an onReset handler 
and any custom container that has marked itself as resettable.

*/
package com.frontalcode 
{
	import flash.utils.*;
	import flash.events.*;

	/**
	 * The DocumentElement class is the base class for all Frontal tags.
	 */
    public class DocumentElement extends OptionableObject
    {
		private static var nextId : Number = 1;
		
		public var document : Document;
		public var parent : DocumentElement;
		public var node : XML;
		public var unparsedChildrenNodes : XMLList;
		public var attributes : Attributes;
		public var elements : Array = [ ];
		
		/**
		 * @private
		 */
		public var id : Number;
		
		/**
		 * @private
		 */
		internal var listeners : Array = [ ];
		
		public var dynamic : Object = { };
		
		/*
			Variables: deepLinkDelayTaskId
			
				If deep-link-delay is a number then deepLinkDelayTaskId is the
				scheduler task for the delayed processing.
		*/
		/**
		 * @private
		 */
		public var deepLinkDelayTaskId : uint = 0;
		
		/**
		 * @private
		 */
		public var deepLinkInitialized : Boolean = false;
		
		// containers and manageableContainer are filtered from the elements 
		// array. They are updated whenever elements is updated.
		//
		private var _containers : Array = [ ];
		private var _manageableContainers : Array = [ ];
		
		private var _isManageable : Boolean = true;
		
		public var childrenManager : ContainerManager;
		
		// An element that wants resets will have a listener added to the 
		// nearest resettable object which may be itself.
		//
		/**
		 * @private
		 */
		internal var _wantsReset : Boolean;
		
		public var currentResetDispatcher : EventDispatcher;
		
		// If we are a sibling of a manager then we may not get our initial
		// reset until we are actually transitioned to. This flag lets us track
		// if this is the first reset or not. We must be listening for a reset
		// to change this value. That is, just dispatching a reset does not
		// affect this. See resetDispatched for that.
		//
		public var isInitialReset : Boolean = true;

		// This flag indicates if this element has dispatched a reset or not. We
		// use this information to decide if other elements that want a reset 
		// and that have this element as their current reset dispatcher but that
		// weren't listening when the reset was dispatched, should be reset 
		// right away. That is, at the time they add the reset listener they go
		// ahead and reset themselves.
		//
		public var resetDispatched : Boolean = false;

		public var firstRender : Boolean = true;
		
		private var resetOnFinalizeRender : Boolean = false;
		private var missedFirstDocumentPreRender : Boolean = false;
		private var _isRendering : Boolean = false;
		protected var _needsRender : Boolean;
		
		/**
		 * @private
		 */
		internal var attributesHaveChanges : Boolean;
		
		private var _needsParse : Boolean = false;
		private var _enabled : Boolean = true;
		private var _selected : Boolean = false;
		private var pseudoClassStates : Object = { };
		private var replayManagerInteractions : Boolean = true;
		
		/*
			Variables: isLink, isVisited and isMarker
			
				If isLink is true then this element is in some way a link. If a
				link then isVisited indicates if it has been traversed or not.
				
				isLink may be made true through the is-link style. By default,
				if this element is a Container, then the system will then 
				listen for the CLICK and DOUBLE_CLICK events on the movie and 
				when received will change isVisited to true and remove the
				listeners.
				
				The exception is if this container is also marked is-marker. In
				that case, we add a listener to whatever container is being
				marked for its VISITED_CHANGE event. When that event is received
				then we alter the marker's isVisited value to match that of the
				marked element. (See <markedContainer> to see how this is
				implemented.)
				
				isVisited may also be set to true if this element is also a
				managed element like a slide in a slideshow for example. In that
				case, selecting this element will also mark it as visited. That
				explains the scenario in which isLink may be false but isVisited
				is true.
				
				If is-marker is set then we also add an event listener to the
				ContainerManager it is associated with (via the mgrId
				attribute). This event listener will convert events dispatched
				by the ContainerManager into interaction run on the marker.
				
				A ContainerManager's Containers may be arranged hierarchically
				via the parentElemNdx attribute. This hierarchy is then used to
				set the hierarchical pseudoclasses siblingSelected, 
				parentSelected, childSelected and nephewSelected on the 
				Container as well as on any markers for that Container. In some
				cases, though, a marker wants its hierarchical pseudoclasses to
				be derived from a Container other than the one it is marking.
				This is the case of a section button in a menu that when clicked
				causes a jump to a subsection. To handle this case, we track
				the markedElement separately from the hierarchyElement.
		*/
		/**
		 * @private
		 */
		private var _isLink : Boolean = false;

		/**
		 * @private
		 */
		private var _isVisited : Boolean = false;

		/**
		 * @private
		 */
		private var _isMarker : Boolean = false;

		/**
		 * @private
		 */
		private var _markerManager : DocumentElement;

		/**
		 * @private
		 */
		private var _markedElement : DocumentElement;

		/**
		 * @private
		 */
		private var _hierarchyElement : DocumentElement;
		
		/*
			Variable: processesOwnContent
			
				The property marks those elements that use their child
				containers themselves and so they should not be parsed by the
				Document. This includes DocElemScript, DocElemStyles and
				ContainerText. We track this property so, for example, if we
				encounter a text node that does not have a parent with
				hasTextContent set to true then we can convert it to a
				ContainerText.
		*/
		public var processesOwnContent : Boolean = false;
		
		public var debugOptions : Object = { };
		
		/**
		 * Creates a new DocumentElement instance.
		 * 
		 * @param document	The document this element belongs to.
		 * @param parent	The element's parent in the document tree.
		 * @param node		The XML node this element is associated with.
		 * @param options	Options affecting the behavior of this instance.
		 * 					(This object does not have built-in support for any
		 * 					options.)
		 */
		public function DocumentElement ( document : Document, parent : DocumentElement, node : XML, options : Object = null )
		{
			super ( options );
			
			this.document = document;
			this.parent = parent;
			this.node = node;
			
			id = nextId++;
			document.registerDocumentElement ( this );
			
			attributes = new Attributes ( document.stylesManager, this );
			
			refreshDebugOptions ( );
			
			needsRender = true;
			
			if ( getStyle ( "processes-own-content" ) ) processesOwnContent = true;
			
			addListener ( this, FrontalEvent.RESET, resetDispatchedHandler );
			
			runInteractions ( "onConstruct" );
			
			if ( document.isRendering )
			{
				missedFirstDocumentPreRender = true;
			}
			else
			{
				addListener ( document, FrontalEvent.PRE_RENDER, firstDocumentPreRenderHandler );
			}
		}
		
		internal function refreshDebugOptions ( ) : void
		{
			debugOptions = { };
			
			if ( node.@id != null )
			{
				var options : Object = document.getObjectOption ( "traceElems" );
				if ( options != null && options [ node.@id ] != null )
				{
					debugOptions = options [ node.@id ];
				}
			}
		}
		
		/**
		 * @private
		 */
		override public function destroy ( ) : void 
		{ 
			while ( elements.length > 0 ) elements [ 0 ].destroy ( );
			
			super.destroy ( );
			document.unregisterDocumentElement ( this ); 
			
			if ( parent != null ) /* Baby's first programming: o v  v vxc dfv  jjjjjjjjjjjjjjjjjjj */
			{
				// Remove our node from the XML document.
				//
				var index : uint = node.childIndex ( );
				delete node.parent ( ) [ index ];
				
				parent.removeChild ( this );
			}
		}
		
		public function get manager ( ) : DocumentManager { return DocumentManager.getInstance ( ); }
		public function get mgr ( ) : DocumentManager { return manager; }
		
		public function get parentContainer ( ) : Container { return parent as Container; }
		
		public function get isManaged ( ) : Boolean { return parent is Container && parentContainer.hasManager; }
		public function get hasManager ( ) : Boolean { return childrenManager != null; }
		
		public function getStyle ( style : String, forceLocal : String = null ) : * { return attributes.styles.getStyle ( style, forceLocal ); }
		public function setStyle ( style : String, value : *, rawValue : Boolean = false, doesNotNeedRender : Boolean = false, isOriginal : Boolean = true ) : void { attributes.styles.setStyle ( style, value, rawValue, doesNotNeedRender, isOriginal ); }
		public function gS ( style : String, forceLocal : String = null ) : * { return getStyle ( style, forceLocal ); }
		public function sS ( style : String, value : *, rawValue : Boolean = false, doesNotNeedRender : Boolean = false, isOriginal : Boolean = true ) : void { setStyle ( style, value, rawValue, doesNotNeedRender, isOriginal ); }
		public function prepStyleTween ( ) : void { attributes.styles.untouchStyles ( ); }
		public function tweenStyles ( duration : Number = NaN ) : void { attributes.styles.tweenStyleChanges ( duration ); }
		public function stopStyleTween ( ) : void { attributes.styles.stopStyleTween ( ); }
		public function fforwardStyleTween ( ) : void { attributes.styles.fforwardStyleTween ( ); }
		
		public function getAttribute ( name : String, namespace : * = null ) : * { return attributes.getAttribute ( name, namespace ); }
		public function attributeExists ( name : String, namespace : * = null ) : * { return attributes.attributeExists ( name, namespace ); }
		public function setAttribute ( name : String, value : *, namespace : String = null, doesNotNeedRender : Boolean = false, isOriginal : Boolean = true ) : void { attributes.setAttribute ( name, value, namespace, doesNotNeedRender, isOriginal ); }
		public function gA ( name : String, namespace : * = null ) : * { return getAttribute ( name, namespace ); }
		public function sA ( name : String, value : *, namespace : String = null, doesNotNeedRender : Boolean = false, isOriginal : Boolean = true ) : void { setAttribute ( name, value, namespace, doesNotNeedRender, isOriginal ); }
		public function getAttributes ( namespace : * = null ) : Array { return attributes.getAttributes ( namespace ); }
		public function getNamespaces ( ) : Array { return attributes.getNamespaces ( ); }
		
		public function get isRendering ( ) : Boolean { return _isRendering; }
		
		public function get needsRender ( ) : Boolean
		{
			if ( getStyle ( "disable-rendering" ) ) return false;
			return _needsRender;
		}
		
		public function set needsRender ( value : Boolean ) : void
		{
			if ( value != _needsRender )
			{
				_needsRender = value;
				
				if ( debugOptions [ "render" ] ) trace ( node.@id, "needsRender", value );
			
				// Also mark our parent as needing to be rendered.
				//
				// XXX There are various optimizations we can do here to see if
				// we really need to propagate this dirty bit. But then we need
				// to be able to render from a node
				//
				if ( value && parent != null ) parent.needsRender = true;
			}
		}
		
		public function get needsParse ( ) : Boolean { return _needsParse; }
		
		public function set needsParse ( value : Boolean ) : void
		{
			if ( value != _needsParse )
			{
				_needsParse = value;
				
				if ( debugOptions [ "render" ] ) trace ( node.@id, "needsParse", value );
			
				if ( value )
				{
					needsRender = true;
					document.registerDirtyDocElem ( this );
				}
			}
		}
		
		/*
			Function: reparseAttributes
			
				This function will reapply all the style sheets associated with
				this element.
		*/
		/**
		 * @private
		 */
		public function reparseAttributes ( ) : void
		{
			if ( needsParse )
			{
				needsParse = false;
				attributes.reparse ( );
				
				if ( debugOptions [ "render" ] ) 
				{
					trace ( node.@id, "matchingSelectors: " );
					for each ( var selector : * in attributes.selectors )
					{
					   trace ( " ", selector.name );
					}
					
					trace ( node.@id, "currentStyles: " );
					for ( var style : String in attributes.styles.styles )
					{
					   trace ( " ", style, ":", attributes.styles.styles [ style ].value );
					}
				}
			}
		}
		
		/**
		 * @private
		 */
		internal function get renderProcessDelayed ( ) : Boolean { return document.processDelayed; }
		
		/*
			Function: firstDocumentRenderHandler
				
				This handler is called after the first render of the document
				after the construction of this element.
		*/
		/**
		 * @private
		 */
		internal function firstDocumentPreRenderHandler ( event : FrontalEvent ) : void 
		{ 
			removeListener ( document, FrontalEvent.PRE_RENDER, firstDocumentPreRenderHandler );
			onFirstDocumentPreRender ( );
		}

		/**
		 * @private
		 */
		public function onFirstDocumentPreRender ( ) : void { runInteractions ( "onFirstDocumentPreRender" ); }
		
		public function render ( ) : void 
		{ 
			if ( ! needsRender ) return;
			
			if ( debugOptions [ "render" ] ) trace ( node.@id, "render" );
			
			// Clear our needsRender flag here rather than in finalizeRender so 
			// that if it gets set again during the render process it won't get 
			// clobbered.
			//
			initializeRender ( );
			renderChildren ( );
	
			if ( ! renderProcessDelayed )
			{
				renderSelf ( );
				finalizeRender ( );
			}
		}
		
		/**
		 * @private
		 */
		public function initializeRender ( ) : void 
		{
			_isRendering = true;
			if ( missedFirstDocumentPreRender )
			{
				missedFirstDocumentPreRender = false;
				onFirstDocumentPreRender ( );
			}
			dispatchEvent ( new FrontalEvent ( FrontalEvent.PRE_RENDER ) );
			needsRender = false;
			attributesHaveChanges = attributes.hasChanges ( );
			attributes.clearChanges ( );
			attributes.styles.clearChanges ( "layout" );
		}
		
		/**
		 * @private
		 */
		public function renderChildren ( ) : void
		{
			for ( var i : uint = 0; i < elements.length; i++ )
			{
				elements [ i ].render ( );
	
				// Break out if we've exceeded our processing time.
				//
				if ( renderProcessDelayed ) break;
			}
		}
		
		/**
		 * @private
		 */
		public function renderSelf ( ) : void
		{
			applyInteractions ( );
			removeUntouchedInteractions ( );
			applyStyles ( );
		}
		
		/**
		 * @private
		 */
		public function applyStyles ( ) : void
		{
			isLink		= getStyle ( "is-link" );
			isMarker	= getStyle ( "is-marker" );
			enabled		= ! getStyle ( "disabled" );
			if ( getStyle ( "wants-reset" ) != null ) wantsReset = getStyle ( "wants-reset" );
			runInteractions ( "onApplyStyles" );
		}
		
		public function set isLink ( value : Boolean ) : void
		{
			if ( value != _isLink )
			{
				_isLink = value;
				dispatchEvent ( new FrontalEvent ( FrontalEvent.LINK_CHANGE ) );
			}
		}
		
		public function get isLink ( ) : Boolean { return _isLink; }
		
		public function set isVisited ( value : Boolean ) : void
		{
			if ( value != _isVisited )
			{
				_isVisited = value;
				dispatchEvent ( new FrontalEvent ( FrontalEvent.VISITED_CHANGE ) );
			}
		}
		
		public function get isVisited ( ) : Boolean { return _isVisited; }
		
		public function set isMarker ( value : Boolean ) : void
		{
			if ( value != _isMarker )
			{
				_isMarker = value;
				
				if ( value )
				{
					// Any DocumentElement may be a marker which means it listens to a
					// ContainerManager and runs interactions as it dispatches events.
					//
					var mgrId : String = getAttribute ( "mgrId" );
					var mgr : DocumentElement = getElementById ( mgrId );
					if ( 
							mgr == null || 
							( ! Util.isEmpty.test ( getAttribute ( "elemNdx" ) ) && mgr.getContainerByIndex ( getAttribute ( "elemNdx" ) ) == null ) ||
							( ! Util.isEmpty.test ( getAttribute ( "hierElemNdx" ) ) && mgr.getContainerByIndex ( getAttribute ( "hierElemNdx" ) ) == null )
						)
					{
						// Our manager or one of our marker-related elements
						// has not have been parsed yet so set up a listener on
						// the Document for new elements.
						//
						addListener ( document, FrontalEvent.ADD_ELEMENT, checkForManagerHandler );
					}
					else
					{
						listenToManagerAsMarker ( mgr );
					}
				}
				else
				{
					removeListener ( document, FrontalEvent.ADD_ELEMENT, checkForManagerHandler );
					
					// Remove any previously registered manager listeners.
					//
					if ( _markerManager != null )
					{
						removeListener ( _markerManager, ContainerMgrEvent.WILL_SELECT,		markerEventHandler );
						removeListener ( _markerManager, ContainerMgrEvent.SELECT,			markerEventHandler );
						removeListener ( _markerManager, ContainerMgrEvent.PROGRESS,		markerEventHandler );
						removeListener ( _markerManager, ContainerMgrEvent.TRANSITION_END,	markerEventHandler );
						removeListener ( _markerManager, ContainerMgrEvent.PAUSE,			markerEventHandler );
						removeListener ( _markerManager, ContainerMgrEvent.RESUME,			markerEventHandler );
						removeListener ( _markerManager, ContainerMgrEvent.HOLD_PROGRESS,	markerEventHandler );
						_markerManager = null;
					}
				}
			}
		}
		
		public function get isMarker ( ) : Boolean { return _isMarker; }
		
		/**
		 * @private
		 */
		internal function checkForManagerHandler ( event : FrontalEvent ) : void
		{
			var mgr : DocumentElement = getElementById ( getAttribute ( "mgrId" ) );
			
			if ( 
					mgr != null && 
					( Util.isEmpty.test ( getAttribute ( "elemNdx" ) ) || mgr.getContainerByIndex ( getAttribute ( "elemNdx" ) ) != null ) &&
					( Util.isEmpty.test ( getAttribute ( "hierElemNdx" ) ) || mgr.getContainerByIndex ( getAttribute ( "hierElemNdx" ) ) != null )
				)
			{
				// We found our manager and other marker-related nodes.
				//
				removeListener ( document, FrontalEvent.ADD_ELEMENT, checkForManagerHandler );
				listenToManagerAsMarker ( mgr );
			}
		}
		
		/**
		 * @private
		 */
		internal function listenToManagerAsMarker ( mgr : DocumentElement ) : void
		{
			if ( ! ( mgr is ContainerManager ) )
			{
				Debugger.logMessage ( Debugger.ERROR, "DocumentElement", "the attribute mgrId \"" + getAttribute ( "mgrId" ) + "\" does not refer to a manager tag." );
			}
			else
			{
				_markerManager = mgr;
				
				addListener ( mgr, ContainerMgrEvent.WILL_SELECT,		markerEventHandler );
				addListener ( mgr, ContainerMgrEvent.SELECT,			markerEventHandler );
				addListener ( mgr, ContainerMgrEvent.PROGRESS,			markerEventHandler );
				addListener ( mgr, ContainerMgrEvent.TRANSITION_END,	markerEventHandler );
				addListener ( mgr, ContainerMgrEvent.PAUSE,				markerEventHandler );
				addListener ( mgr, ContainerMgrEvent.RESUME,			markerEventHandler );
				addListener ( mgr, ContainerMgrEvent.HOLD_PROGRESS,		markerEventHandler );
				
				if ( getStyle ( "wants-reset" ) == null ) setStyle ( "wants-reset", true );
				
				if ( ! Util.isEmpty.test ( getAttribute ( "elemNdx" ) ) ) 
				{
					var container : DocumentElement = mgr.getContainerByIndex ( getAttribute ( "elemNdx" ) );
					markedElement = container;
					if ( container == null ) Debugger.logMessage ( Debugger.WARNING, "DocumentElement", "cannot find marked element \"" + getAttribute ( "elemNdx" ) + "\" on manager \"" + getAttribute ( "mgrId" ) + "\"" );
				}
				
				if ( ! Util.isEmpty.test ( getAttribute ( "hierElemNdx" ) ) || markedElement != null ) 
				{
					container = ! Util.isEmpty.test ( getAttribute ( "hierElemNdx" ) ) ? mgr.getContainerByIndex ( getAttribute ( "hierElemNdx" ) ) : markedElement;
					hierarchyElement = container;
					if ( container == null ) Debugger.logMessage ( Debugger.WARNING, "DocumentElement", "cannot find hierarchy element \"" + getAttribute ( "hierElemNdx" ) + "\" on manager \"" + getAttribute ( "mgrId" ) + "\"" );
				}
				
				// Handle case that the ContainerManager has already been reset
				// which case we will not receive a select signal so we fake 
				// one.
				//
				// We do this after we've set markedElement above to better
				// simulate what would have happened had we registered before
				// the marked element were selected.
				//
				if ( ! mgr.isInitialReset ) 
				{
					var event : ContainerMgrEvent = new ContainerMgrEvent ( ContainerMgrEvent.SELECT, mgr as ContainerManager, 0, true );
					if ( ( mgr as ContainerManager ).next == null )
					{
						event.next = ( mgr as ContainerManager ).current;
						event.current = null;
					}
					else
					{
						event.next = ( mgr as ContainerManager ).next;
						event.current = ( mgr as ContainerManager ).current;
					}
					runInteractions ( 'onSelect', { event: event } );
				}
			}
		}
		
		/**
		 * @private
		 */
		internal function markerEventHandler ( event : ContainerMgrEvent ) : void
		{
			runInteractions ( 'on' + Util.capitalize ( event.type ), { event: event } );
		}
		
		public function set markedElement ( value : DocumentElement ) : void
		{
			if ( ! getStyle ( "is-marker" ) && value != null ) Debugger.logMessage ( Debugger.WARNING, "DocumentElement", "setting markedElement on a non-marker element" );
	
			if ( value !== _markedElement )
			{
				if ( _markedElement != null ) 
				{
					removeListener ( _markedElement, FrontalEvent.VISITED_CHANGE,  markedElementChangeHandler );
					removeListener ( _markedElement, FrontalEvent.SELECTED_CHANGE, markedElementChangeHandler );
				}
				_markedElement = value;
				if ( _markedElement != null ) 
				{
					addListener ( _markedElement, FrontalEvent.VISITED_CHANGE,  markedElementChangeHandler );
					addListener ( _markedElement, FrontalEvent.SELECTED_CHANGE, markedElementChangeHandler );
					isVisited = _markedElement.isVisited;
					selected  = _markedElement.selected;
					enabled = true;
				}
			}
			
			// We do this step regardless of the previous value of 
			// _markedElement since the uninitialized value of the variable is
			// equal to null.
			//
			if ( value == null )
			{
				isVisited = false;
				selected = false;
				enabled = false;
			}
		}
		
		public function get markedElement ( ) : DocumentElement { return _markedElement; }
		
		/**
		 * @private
		 */
		internal function markedElementChangeHandler ( event : FrontalEvent ) : void 
		{ 
			var target : DocumentElement = event.target as DocumentElement;
			isVisited = target.isVisited; 
			selected  = target.selected; 
		}
		
		public function set hierarchyElement ( value : DocumentElement ) : void
		{
			if ( ! isMarker && value != null ) Debugger.logMessage ( Debugger.WARNING, "DocumentElement", "setting hierarchyElement on a non-marker element" );
			
			if ( value !== _hierarchyElement )
			{
				if ( _hierarchyElement != null ) 
				{
					removeListener ( _hierarchyElement, FrontalEvent.PSEUDO_CLASS_STATE_CHANGE,	hierarchyElementChangeHandler );
				}
				_hierarchyElement = value;
				if ( _hierarchyElement != null ) 
				{
					addListener ( _hierarchyElement, FrontalEvent.PSEUDO_CLASS_STATE_CHANGE,	hierarchyElementChangeHandler );
					
					// Mirror the hierarchy-related pseudoclass states.
					//
					setPseudoClassState ( "nephewSelected",		_hierarchyElement.getPseudoClassState ( "nephewSelected" ) );
					setPseudoClassState ( "childSelected",		_hierarchyElement.getPseudoClassState ( "childSelected" ) );
					setPseudoClassState ( "parentSelected",		_hierarchyElement.getPseudoClassState ( "parentSelected" ) );
					setPseudoClassState ( "siblingSelected",	_hierarchyElement.getPseudoClassState ( "siblingSelected" ) );
				}
			}
			
			// We do this step regardless of the previous value of 
			// _hierarchyElement since the uninitialized value of the variable 
			// is equal to null.
			//
			if ( value == null )
			{
				setPseudoClassState ( "nephewSelected",		0 );
				setPseudoClassState ( "childSelected",		0 );
				setPseudoClassState ( "parentSelected",		0 );
				setPseudoClassState ( "siblingSelected",	false );
			}
		}
		
		public function get hierarchyElement ( ) : DocumentElement { return _hierarchyElement; }
		
		/**
		 * @private
		 */
		internal function hierarchyElementChangeHandler ( event : FrontalEvent ) : void 
		{ 
			var target : DocumentElement = event.target as DocumentElement;
			setPseudoClassState ( "nephewSelected",		target.getPseudoClassState ( "nephewSelected" ) );
			setPseudoClassState ( "childSelected",		target.getPseudoClassState ( "childSelected" ) );
			setPseudoClassState ( "parentSelected",		target.getPseudoClassState ( "parentSelected" ) );
			setPseudoClassState ( "siblingSelected",	target.getPseudoClassState ( "siblingSelected" ) );
		}
		
		/*
			Function: getMappedStyleValue
			
				In cases where the style maps to a movie, text, video or some
				other property, then we can get a current value for it. Our
				subclasses will determine this.
				
			Parameters:
			
				style	- The style of interest.
				
			Returns:
			
				The mapped value of the style.
		*/
		/**
		 * @private
		 */
		public function getMappedStyleValue ( style : String ) : * { return undefined; }
		
		/**
		 * @private
		 */
		public function finalizeRender ( ) : void
		{		
			if ( firstRender )
			{
				runInteractions ( "onFirstRender" );
				firstRender = false;
			}
			
			document.nodesProcessed++;
			dispatchEvent ( new FrontalEvent ( FrontalEvent.RENDER ) );
			
			// We've noticed that the element decided it wanted a reset after 
			// the reset was sent. Here we do that reset.
			//
			if ( resetOnFinalizeRender )
			{
				resetOnFinalizeRender = false;
				reset ( );
			}
			
			_isRendering = false;
		}
		
		/**
		 * @private
		 */
		public function show ( force : * = null ) : void { }
		
		public function addChild ( element : DocumentElement ) : void 
		{ 
			elements.push ( element );
			
			// Our position is dependent on our node's position.
			//
			elements.sort ( function ( a : DocumentElement, b : DocumentElement ) : Number { return a.node.childIndex ( ) < b.node.childIndex ( ) ? -1 : 1; } );  
			
			updateFilteredElementArrays ( );
			if ( element is ContainerManager ) childrenManager = element as ContainerManager;
		}
		public function get elementsChildIndex ( ) : int { return parent == null ? -1 : parent.elements.indexOf ( this ); }
		public function get childIndex ( ) : int { return parent == null ? -1 : parent.containers.indexOf ( this ); }
		public function removeChild ( element : DocumentElement ) : void
		{
			// This only removes the child from its parent. Use destroy() to 
			// also remove it graphical presense and it presense in the XML
			// document.
			//
			var index : int = element.elementsChildIndex;
			if ( index >= 0 ) 
			{
				elements.splice ( index, 1 );
				updateFilteredElementArrays ( );
				if ( childrenManager === element ) childrenManager = null;
			}
		}
		private function updateFilteredElementArrays ( ) : void
		{
			_containers = elements.filter ( function ( item : *, index : int, array : Array ) : Boolean { return item is Container; } );
			_manageableContainers = elements.filter ( function ( item : *, index : int, array : Array ) : Boolean { return item is Container && ( item as DocumentElement ).isManageable; } );
		}
		
		public function get containers ( ) : Array { return _containers; }
		public function get manageableContainers ( ) : Array { return _manageableContainers; }
		
		/*
			Variable: isManageable
			
				If a container is manageable then it will show up in the 
				manageableContainers list which ContainerManagers use in their
				operations. Non-manageable Containers include for example
				progress indicators.
		*/
		public function set isManageable ( value : Boolean ) : void
		{
			if ( _isManageable != value )
			{
				_isManageable = value;
				if ( parent != null ) parent.updateFilteredElementArrays( );
			}
		}

		public function get isManageable ( ) : Boolean { return _isManageable; }

		/**
		 * @private
		 */
		internal function resetDispatchedHandler ( event : FrontalEvent ) : void
		{
			resetDispatched = true;
			removeListener ( this, FrontalEvent.RESET, resetDispatchedHandler );
		}
		
		public function reset ( ) : void 
		{
			prepareAssets ( );
			isInitialReset = false;
			
			// If we are our own reset dispatcher then we do no dispatch a reset
			// event in order to avoid an infinite loop.
			//
			if ( currentResetDispatcher !== this ) dispatchEvent ( new FrontalEvent ( FrontalEvent.RESET ) );
		}
		
		/*
			Function: wantsReset
			
				If wantsReset is set then we check if we are a marker or not. If
				we are a marker then our ContainerManager is our reset
				dispatcher otherwise we scan up the DocumentElement tree until
				we find a Container with a manager or the Document. Whichever we
				find becomes our reset dispatcher. We then add or remove a
				listener (depending on what wantsRest is being set to) that will
				call reset on the DocumentElement.
				
			Parameters:
			
				value	- The new value for wantReset.
		*/
		public function set wantsReset ( value : Boolean ) : void
		{
			_wantsReset = value;
			
			var resetDispatcher : DocumentElement;
			
			if ( getStyle ( "reset-dispatcher" ) )
			{
				resetDispatcher = getElementById ( getStyle ( "reset-dispatcher" ) );
			}
			else if ( getStyle ( "is-marker" ) )
			{
				var mgrId : String = getAttribute ( "mgrId" );
				resetDispatcher = getElementById ( mgrId );
				
				// If the manager is null at this point that's fine because we
				// setup an event listener in applyStyles() that will call
				// wantsReset when the manager does come online.
				//
			}
			else
			{
				resetDispatcher = this;
				
				while ( resetDispatcher != document )
				{
					if ( resetDispatcher is Container && ( resetDispatcher as Container ).isManaged ) break;
					resetDispatcher = resetDispatcher.parent;
				}
			}
			
			if ( currentResetDispatcher != null && ( currentResetDispatcher != resetDispatcher || ! value ) )
			{
				removeListener ( currentResetDispatcher, FrontalEvent.RESET, resetHandler );
				removeListener ( currentResetDispatcher, FrontalEvent.PREPARE_ASSETS, prepareAssetsHandler );
				if ( currentResetDispatcher is Document ) removeListener ( currentResetDispatcher, FrontalEvent.INITIAL_RESET, initialResetHandler );
				currentResetDispatcher = null;
			}
			
			if ( value && currentResetDispatcher != resetDispatcher )
			{
				// Listen for reset events. These events are dispatched from the
				// Document (in releaseResets()), or from a ContainerManager (in
				// reset() for DocumentElements that are markers, e.g., have the
				// style "is-marker" set to true) or from a managed document
				// (through dispatchContainerEvents() in ContainerMgrEvent). 
				//
				// There's a possibility that we are adding our listener after
				// the dispatcher has sent its event. If that seems like the
				// case then we reset ourselves. If it's a managed Container 
				// then we also test that the manager's current state matches 
				// our reset dispatcher's reset styles.
				//
				currentResetDispatcher = resetDispatcher;
				addListener ( resetDispatcher, FrontalEvent.RESET, resetHandler );
				addListener ( resetDispatcher, FrontalEvent.PREPARE_ASSETS, prepareAssetsHandler );
				if ( resetDispatcher is Document && isInitialReset ) addListener ( resetDispatcher, FrontalEvent.INITIAL_RESET, initialResetHandler );
				
				if ( resetDispatcher.resetDispatched )
				{
					if ( resetDispatcher.isManaged )
					{
						// 5005: Unknown error optimizing byte code.
						// var mgr : ContainerManager = resetDispatcher.parentContainer.childrenManager;
						var mgr : ContainerManager = ( resetDispatcher.parent as Container ).childrenManager;
						
						if ( 
								( resetDispatcher.getStyle ( "reset-on-select"                  ) && ( ( mgr.next == null && resetDispatcher === mgr.current ) || ( mgr.next != null && resetDispatcher === mgr.next ) ) ) ||
								( resetDispatcher.getStyle ( "reset-on-deselect"                ) && ( ( mgr.next == null && resetDispatcher !== mgr.current ) || ( mgr.next != null && resetDispatcher !== mgr.next ) ) ) ||
								( resetDispatcher.getStyle ( "reset-on-select-transition-end"   ) && mgr.next == null && resetDispatcher === mgr.current ) ||
								( resetDispatcher.getStyle ( "reset-on-deselect-transition-end" ) && mgr.next == null && resetDispatcher !== mgr.current ) ||
								( resetDispatcher.getStyle ( "reset-on-will-select"             ) && resetDispatcher === mgr.afterNext )
							)
						{
							resetOnFinalizeRender = true;
						}
					}
					else
					{
						resetOnFinalizeRender = true;
					}
				}
			}
		}
		
		public function get wantsReset ( ) : Boolean { return _wantsReset; }

		/**
		 * @private
		 */
		internal function resetHandler ( event : FrontalEvent ) : void { reset ( ); }

		/**
		 * @private
		 */
		internal function initialResetHandler ( event : FrontalEvent ) : void 
		{
			removeListener ( event.target as EventDispatcher, FrontalEvent.INITIAL_RESET, initialResetHandler );
			runInteractions ( "onInitialReset", { event : event } );
			reset ( ); 
		}
		
		/**
		 * @private
		 */
		internal function prepareAssetsHandler ( event : FrontalEvent ) : void { prepareAssets ( ); }

		public function prepareAssets ( ) : void { runInteractions ( "onPrepareAssets" ); }
		
		public function prepareDescendantAssets ( forward : Boolean = true ) : void 
		{ 
			if ( forward ) prepareAssets ( );
			for ( var i : uint = forward ? 0 : elements.length - 1; i >= 0 && i < elements.length; i += forward ? 1 : -1 ) elements [ i ].prepareDescendantAssets ( forward ); 
			if ( ! forward ) prepareAssets ( );
		}
		
		public function set enabled ( value : Boolean ) : void 
		{ 
			if ( _enabled != value )
			{
				_enabled = value;
				attributes.styles.setStyle ( "disabled", ! value, false, true, true );
				dispatchEvent ( new FrontalEvent ( FrontalEvent.ENABLED_CHANGE ) );
			}
		}
		
		public function get enabled ( ) : Boolean { return _enabled; }
		
		public function set selected ( value : Boolean ) : void 
		{ 
			if ( _selected != value )
			{
				_selected = value;
				dispatchEvent ( new FrontalEvent ( FrontalEvent.SELECTED_CHANGE ) );
				if ( value ) isVisited = true;
			}
		}
		
		public function get selected ( ) : Boolean { return _selected; }
		
		/*
			Function: setPseudoClassState
				
				In our stylesheet selectors, we support custom pseudoclasses.
				For example:
				
					*:myPseudoClass { style: value; }
					*:myPseudoClass2(value)	{ style: value; }
					
				Use this function to change the value of a pseudoclass on a
				DocumentElement.
				
				The stylesheet parser enforces that pseudoclass names should 
				start with an underscore or a letter and consist of underscores,
				letter, numbers and hyphens.
				
			Parameters:
			
				state	- The name of the custom pseudoclass.
				value	- Whether the element has entered the pseudoclass (true)
						  or left it (false).
		*/
		public function setPseudoClassState ( state : String, value : * ) : void 
		{ 
			if ( ! pseudoClassStates.hasOwnProperty ( state ) || pseudoClassStates [ state ] != value )
			{
				pseudoClassStates [ state ] = value;
				dispatchEvent ( new FrontalEvent ( FrontalEvent.PSEUDO_CLASS_STATE_CHANGE ) );
			}
		}
		
		/*
			Function: getPseudoClassState
				
				In our stylesheet selectors, we support custom pseudoclasses.
				For example:
				
					*:myPseudoClass { style: value; }
					
				Use this function to return the custom pseudoclass state of a
				DocumentElement. 
				
			Parameters:
			
				state	- The name of the custom pseudoclass.
				
			Returns:
			
				The custom pseudoclass state or undefined if this pseudoclass 
				has not been defined on this DocumentElement.
		*/
		public function getPseudoClassState ( state : String ) : * 
		{
			// Support a few default pseudoclasses.
			//
			if ( state == "nth-container" )
			{
				return childIndex + 1;
			}
			else if ( state == "nth-element" )
			{
				return elementsChildIndex + 1;
			}
			
			return pseudoClassStates [ state ]; 
		}

		public function getElementByIndex ( index : *, stopAtChildren : Boolean = true, showWarnings : Boolean = true, includeNonContainers : Boolean = false ) : DocumentElement
		{
			var target : DocumentElement = null;
			
			// Look for a 1-based index.
			//
			if ( 
				! isNaN ( index ) && Number ( index ) > 0 && 
				( 
					( ! includeNonContainers && Number ( index ) <= containers.length ) ||
					(   includeNonContainers && Number ( index ) <= elements.length ) 
				)
			)
			{
				target = includeNonContainers ? elements [ Number ( index ) - 1 ] : containers [ Number ( index ) - 1 ];
			}
			else
			{
				// Find it by name or id.
				//
				var targetNode : XMLList;
				if ( stopAtChildren )
				{
					targetNode = node.children ( ).( attribute ( "name" ).length ( ) > 0 && @name == index );
				}
				else
				{
					targetNode = node.descendants ( ).( attribute ( "name" ).length ( ) > 0 && @name == index );
				}
				if ( targetNode.length ( ) > 0 && ! includeNonContainers ) 
				{
					// Winnow the list to only include containers.
					//
					var results : XMLList = new XMLList ( );
					for ( var i : uint = 0; i < targetNode.length ( ); i++ )
					{
						if ( document.getNodeElement ( targetNode [ i ] ) is Container ) results += targetNode [ i ];
					}
					targetNode = results;
				}
				if ( targetNode.length ( ) > 1 && showWarnings ) Debugger.logMessage ( Debugger.INFO, "DocumentElement", "found multiple elements with index \"" + index + "\"" );
				if ( targetNode.length ( ) > 0 ) target = document.getNodeElement ( targetNode [ 0 ] );
				
				if ( target == null ) target = getElementById ( index, showWarnings );
			}
			
			return target;
		}
		
		public function getElementById ( id : String, showWarnings : Boolean = true ) : DocumentElement
		{
			var target : DocumentElement = null;
			
			var targetNode : * = document.documentElementCacheById [ id ];
			if ( targetNode !== undefined && targetNode !== null && ( targetNode as XMLList ).length ( ) > 0 )
			{
				if ( ( targetNode as XMLList ).length ( ) > 1 && showWarnings ) Debugger.logMessage ( Debugger.INFO, "DocumentElement", "found multiple elements with id \"" + id + "\"" );
				target = document.getNodeElement ( ( targetNode as XMLList ) [ 0 ] );
			}
			if ( target == null && showWarnings ) 
			{
				Debugger.logMessage ( Debugger.INFO, "DocumentElement", "cannot find element with id \"" + id + "\"" );
			}

			return target;
		}
		
		// Convenience functions.
		//
		public function gE 					( index : *, stopAtChildren : Boolean = true, showWarnings : Boolean = true, includeNonContainers : Boolean = false ) : DocumentElement	{ return getElementByIndex ( index, stopAtChildren, showWarnings, includeNonContainers ); }
		public function getContainerByIndex	( index : *, stopAtChildren : Boolean = true, showWarnings : Boolean = true ) : Container		{ return getElementByIndex ( index, stopAtChildren, showWarnings ) as Container; }
		public function gC					( index : *, stopAtChildren : Boolean = true, showWarnings : Boolean = true ) : Container		{ return getElementByIndex ( index, stopAtChildren, showWarnings ) as Container; }
		public function getContainerById	( id : String, showWarnings : Boolean = true ) : Container { return getElementById ( id, showWarnings ) as Container; }
		
		/**
		 * @private
		 */
		public function applyInteractions ( ) : void
		{
			if ( ! attributesHaveChanges ) return; 

			// Mark all listeners as untouched sp we can remove unneeded ones
			// later.
			//
			for ( var i : uint = 0; i < listeners.length; i++ ) listeners [ i ].touched = false;
			
			// Now add listeners for any interactions tied to our events.
			//
			applyInteraction ( FrontalEvent.CONSTRUCT );
			applyInteraction ( FrontalEvent.PRE_RENDER );
			applyInteraction ( FrontalEvent.RENDER );
			applyInteraction ( FrontalEvent.RESET );
			applyInteraction ( FrontalEvent.SELECT );
			applyInteraction ( FrontalEvent.DESELECT );
			applyInteraction ( FrontalEvent.SELECT_TRANSITION_END );
			applyInteraction ( FrontalEvent.DESELECT_TRANSITION_END );
			applyInteraction ( FrontalEvent.WILL_SELECT );
			applyInteraction ( FrontalEvent.DESTROY );
			
			// See if we missed any manager events. This is common as when hide-
			// unselected is true, we are not fully rendered the first time 
			// until after the events have been sent. XXX Likely this is a logic
			// bug.
			//
			if ( replayManagerInteractions )
			{
				replayManagerInteractions = false;
				
				if ( parent is Container && ( parent as Container ).childrenManager != null )
				{
					var mgr : ContainerManager = ( parent as Container ).childrenManager;
					if ( ! mgr.isInitialReset )
					{
						if ( ( mgr.current === this && mgr.next == null ) || mgr.next === this ) 
						{
							runInteractions ( 'onSelect', { event: new FrontalEvent ( FrontalEvent.SELECT ) } );
							if ( mgr.next == null ) runInteractions ( 'onSelectTransitionEnd', { event: new FrontalEvent ( FrontalEvent.SELECT ) } );
						}
						if ( mgr.current === this && mgr.next != null ) runInteractions ( 'onDeselect', { event: new FrontalEvent ( FrontalEvent.DESELECT ) } );
						if ( mgr.afterNext === this ) runInteractions ( 'onWillSelect', { event: new FrontalEvent ( FrontalEvent.WILL_SELECT ) } );
					}
				}
			}
		}
		
		internal function removeUntouchedInteractions ( ) : void
		{
			for ( var i : int = 0; i < listeners.length; i++ )
			{
				if ( ! listeners [ i ].touched )
				{
					removeListener ( listeners [ i ].target, listeners [ i ].type, listeners [ i ].handler, listeners [ i ].options != null ? listeners [ i ].options.useCapture : false );
					listeners.splice ( i--, 1 );
				}
			}
		}
		
		internal function findInteractionListener ( type : String, target : Object = null, namespace : * = null, handlerOptions : Object = null ) : Object
		{
			var result : Object;
			
			if ( target == null ) target = this;
			var nsName : String = attributes.getNamespaceURI ( namespace );
			
			for ( var i : uint = 0; i < listeners.length; i++ ) 
			{
				if ( listeners [ i ].target === target && listeners [ i ].type == type && listeners [ i ].namespace == nsName ) 
				{
					var optionsMatch : Boolean = false;
					if ( listeners [ i ].options == null && handlerOptions == null )
					{
						optionsMatch = true;
					}
					else if ( listeners [ i ].options != null && handlerOptions != null )
					{
						optionsMatch = true;
						for ( var option : String in handlerOptions )
						{
							if ( handlerOptions [ option ] != listeners [ i ].options [ option ] )
							{
								optionsMatch = false;
								break;
							}
						}
					}
					if ( optionsMatch )
					{
						result = listeners [ i ];
						break;
					}
				}
			}
			
			return result;
		}
		
		/**
		 * @private
		 */
		public function removeInteraction ( type : String, target : Object = null, namespace : * = null, handlerOptions : Object = null ) : void
		{
			var listener : Object = findInteractionListener ( type, target, namespace, handlerOptions );
			if ( listener != null )
			{
				removeListener ( listener.target, listener.type, listener.handler, listener.options != null ? listener.options.useCapture : false );
				listeners.splice ( listeners.indexOf ( listener ), 1 );
			}
		}
		
		/**
		 * @private
		 */
		public function applyInteraction ( type : String, target : EventDispatcher = null, attribute : String = null, handlerOptions : Object = null ) : Boolean
		{
			if ( target == null ) target = this;
			
			if ( attribute == null ) attribute = "on" + Util.capitalize ( type );

			var applied : Boolean = false;
			
			for each ( var thisNamespace : * in getNamespaces ( ) )
			{
				var thisAttr : String = getAttribute ( attribute, thisNamespace );
				if ( thisAttr != null && thisAttr != "" ) //attributes.attributeChanged ( attribute, thisNamespace ) && ! Util.isEmpty.test ( getAttribute ( attribute, thisNamespace ) ) )
				{
					// See if we already have a listener.
					//
					var listener : Object = findInteractionListener ( type, target, thisNamespace, handlerOptions );
					
					if ( listener == null )
					{
						var handler : Function = function ( event : Event ) : void
							{
								ActionScriptInterpreter.evaluate ( arguments.callee.interaction, arguments.callee.documentElement, { event: event }, { }, null, document );
								
								var options : Object = arguments.callee.options;
								if ( options.preventDefault ) event.preventDefault ( );
								if ( options.stopImmediatePropagation ) event.stopImmediatePropagation ( );
								if ( options.stopPropagation ) event.stopPropagation ( );
							}
							addListener ( target, type, handler, handlerOptions != null ? handlerOptions.useCapture : false );
							listener = { target: target, type: type, namespace: thisNamespace, handler: handler, options: handlerOptions };
							listeners.push ( listener );
					}
					
					listener.handler [ "interaction" ] = getAttribute ( attribute, thisNamespace );
					listener.handler [ "documentElement" ] = this;
					listener.handler [ "options" ] = handlerOptions == null ? { } : handlerOptions;
					listener.touched = true;
					
					applied = true;
				}
			}
			
			return applied;
		}
		
		/*
			Function: runInteractions
			
				The method will run the script associated with the given
				attribute in the given namespace. If no namespace is set then
				all interactions of this name regardless of the namespace will
				be run.
				
			Parameters:
			
				attribute		- The attribute to get the script from, e.g., 
								  "onClick."
				localVariables	- An Object keyed by variable names that should 
								  be pre-populated in the script's scope when it
								  is executed.
				namespace		- A namespace (either a String or a Namespace)
								  to examine for the given attribute. Leave null
								  to run interactions for the given attribute in
								  all namespaces.
								  
			Returns:
			
				If no namespace is given and there is a single result then that
				is the returned value. Otherwise the interaction
				getNamespaceForReturn determines the result. If that interaction
				is not defined then the default namespace is used. If there is
				still no return value then undefined is returned.
			
			Notes:
			
				We use namespace for example when we want to run multiple 
				interactions for a single event. For example, in the following
				we may run some interactive code as well as some analytics code
				on a click event:
				
					<text onClick="..." analytics:onClick="..." />
		*/	
		public function runInteractions ( attribute : String, localVariables : Object = null, namespace : * = null ) : *
		{
			var results : Object = runInteractionsWithResults ( attribute, localVariables, namespace );
			
			var result : * = undefined;
			
			if ( namespace == null )
			{
				var count : uint = 0;
				
				for each ( var value : * in results )
				{
					result = value;
					count++;
				}
				
				if ( count > 1 )
				{
					var resultNS : * = runInteractions ( "getNamespaceForReturn", { attribute: attribute, localVariables: localVariables }, "" );
					result = results [ resultNS == undefined ? "" : resultNS ];
				}
			}
			
			return result;
		}
		
		/*
			Function: runInteractionsWithResults
			
				The method will run the script associated with the given
				attribute in the given namespace. If no namespace is given then
				all namespaces are examined for the given attribute.
				
			Parameters:
			
				attribute		- The attribute to get the script from, e.g., 
								  "onClick."
				localVariables	- An Object keyed by variable names that should 
								  be pre-populated in the script's scope when it
								  is executed.
				namespace		- A namespace (either a String or a Namespace)
								  to examine for the given attribute. Leave null
								  to run interactions for the given attribute in
								  all namespaces.
								  
			Returns:
			
				An Object keyed by the namespaces for which an attribute's 
				script was run and with values set to the return values of the
				scripts.
			
			Notes:
			
				We use namespace for example when we want to run multiple 
				interactions for a single event. For example, in the following
				we may run some interactive code as well as some analytics code
				on a click event:
				
					<text onClick="..." analytics:onClick="..." />
		*/	
		public function runInteractionsWithResults ( attribute : String, localVariables : Object = null, namespace : * = null ) : Object
		{
			var namespaces : Array = namespace == null ? getNamespaces ( ) : [ namespace ];
			var results : Object = { };
			
			if ( namespaces.length > 1 && attribute != "getOrderedNamespaces" )
			{
				// Allow the user to order the interactions. Obviously we skip
				// this check for the getOrderedNamespaces interaction.
				//
				
				var result : * = runInteractions ( "getOrderedNamespaces", { namespaces: namespaces } );
				if ( result === undefined && this !== document ) result = document.runInteractions ( "getOrderedNamespaces", { namespaces: namespaces } );
				namespaces = result === undefined ? namespaces : result;
			}
			
			for ( var i : uint = 0; i < namespaces.length; i++ )
			{
				var thisNamespace : * = namespaces [ i ];
				
				if ( results == null ) results = { };
				
				if ( ! Util.isEmpty.test ( getAttribute ( attribute, thisNamespace ) ) )
				{
					results [ thisNamespace ] = ActionScriptInterpreter.evaluate ( getAttribute ( attribute, thisNamespace ), this, localVariables, { }, null, document );
				}
			}
			
			return results;
		}
		
		/*
			Function: getDeepLinkValue
			
				If this DocumentElement has the style "deep-link" set to "path"
				or "query" then it is a deep link handler. Then as the
				DeepLinkManager processes a DocumentElement's queries as to
				which of its states is currently deep-linked, it will call this
				method. For example, a ContainerManager expects the value of the
				deep link to be an index of one of its managed Containers. This
				method in that case returns the particular Container. For 
				another example, see ContainerVideo which can deep link cues in
				the video.
			
			Parameters:
			
				index	- The deep link index.
				
			Returns:
			
				A value this DocumentElement associates with the index.
		*/
		public function getDeepLinkValue ( index : String ) : * 
		{ 
			return runInteractions ( "onGetDeepLinkValue", { index: index } ); 
		}
		
		/*
			Function: getDeepLinkIndex
			
				This does the inversion of getDeepLinkValue. That is, given the
				return of that method, what is a possible input that leads to 
				it? For a ContainerManager with the style deep-link set to path, 
				this returns getHierarchicalChildName for the value for example. 
			
			Parameters:
			
				value	- The deep link value for which an index is needed.
				
			Returns:
			
				An index this DocumentElement associates with the value.
		*/
		public function getDeepLinkIndex ( value : * ) : String 
		{ 
			return runInteractions ( "onGetDeepLinkIndex", { value: value } ); 
		}
		
		/*
			Function: processDeepLink
			
				If this DocumentElement has the style "deep-link" set to "path"
				or "query" then it is a deep link handler. Then as the
				DeepLinkManager processes a deep-link, it will call first call
				getDeepLinkValue and then call this	method with the result. For 
				example, a ContainerManager will jump to the managed Container 
				returned by getDeepLinkValue as a result of examining the deep 
				link.
			
			Parameters:
			
				index	- The deep link index.
		*/
		public function processDeepLink ( value : * ) : void 
		{ 
			runInteractions ( "onProcessDeepLink", { value: value } ); 
		}
		
		/*
			Function: checkForDeepLink
			
				If this DocumentElement has the style "deep-link" set to "path"
				or "query" then it is a deep link handler. This method is used
				to aid in the initialization of a deep-linked object. As such it
				will query the DeepLinkManager for anything in the initial deep
				link that is relevant to this DocumentElement via getDeepLink.
				If at this point the DeepLinkManager is not initialized then an
				event listener is added to re-call this method after it is
				initialized. If the delayed call to processDeepLink becomes
				undesirable then call the following from the same
				DocumentElement that the initial call was made from:
				
					removeListener ( DeepLinkManager.getInstance ( ), FrontalEvent.INITIALIZED, deepLinkInitializationHandler );
					
				If on the other hand the DeepLinkManager is initialized and the
				method is being called synchronously (and not as the result of
				an event handler) then the deep-link-delay style is examined. If
				that style is not a number then the result will be returned
				immediately. If it is a number then a timer will be started
				based on its value and the value of the style
				deep-link-delay-use-secs and at the end of which processDeepLink
				will be called. Note that in this case too, the result of
				getDeepLink is returned and so the caller should also examine
				the style deep-link-delay to determine if a timer has been
				started. To end this timer, run this command:
				
					Scheduler.getInstance ( ).removeTask ( deepLinkDelayTaskId );
				
			Parameters:
			
				index	- The deep link index.
		*/
		public function checkForDeepLink ( isDelayed : Boolean = false ) : *
		{
			deepLinkInitialized = true;
			
			if ( getStyle ( "deep-link" ) == "none" ) return;
			
			var deepLinkValue : * = DeepLinkManager.getDeepLink ( this, true );
			
			if ( ! DeepLinkManager.initialized )
			{
				addListener ( DeepLinkManager.getInstance ( ), FrontalEvent.INITIALIZED, deepLinkInitializationHandler );
				return undefined;
			}
			
			removeListener ( DeepLinkManager.getInstance ( ), FrontalEvent.INITIALIZED, deepLinkInitializationHandler );
			
			if ( deepLinkValue === undefined ) return deepLinkValue;
			
			if ( isNaN ( getStyle ( "deep-link-delay" ) ) )
			{
				// If we are being called from the initial reset ( ) then it 
				// will look for a non-null deepLinkContainer and use it as the
				// initial Container to jump to. If not then we do the jump
				// ourselves.
				//
				if ( ! isDelayed ) 
				{
					return deepLinkValue;
				}
				else
				{
					processDeepLink ( deepLinkValue );
				}
			}
			else
			{
				// Delay the processing of the deep link.
				//
				if ( getStyle ( "deep-link-delay-use-secs" ) )
				{
					deepLinkDelayTaskId = Scheduler.setTimeout ( processDeepLink, Number ( getStyle ( "deep-link-delay" ) ), true, deepLinkValue );
				}
				else
				{
					deepLinkDelayTaskId = Scheduler.setTimeoutInFrames ( processDeepLink, Number ( getStyle ( "deep-link-delay" ) ), deepLinkValue );
				}
				
				deepLinkValue = null;
			}
			
			return deepLinkValue;
		}
		
		/**
		 * @private
		 */
		public function deepLinkInitializationHandler ( event : FrontalEvent ) : void { checkForDeepLink ( true ); }
		
		/*
			Function: getLoadProgress
			
				Calculate the progress of dynamic objects in the DocumentElement
				and its children. This will also add listeners to those elements
				that have loadable content such that as their progress changes,
				this DocumentElement can report a cumulative change.
				
			Parameters:
			
				addListeners	- If true then listeners will be added to all of
								the loadable DocumentElements encountered during
								the calculation. If false they will not be. This
								is useful to set to false when doing a one-time
								local calculation of the progress on a
								DocumentElement.
			
			Returns:
			
				An Object with members fraction (a number between 0 and 1 
				indicating the current load progress), bytesLoaded and 
				bytesTotal. Also, there is an Array named loadableElements of 
				all the DocumentElements at and below this level that have
				loadable content.
		*/
		/**
		 * @private
		 */
		public function getLoadProgress ( addListeners : Boolean = true ) : Object
		{
			var progress : Object = {
					fraction: 1,
					bytesLoaded: 0,
					bytesTotal: 0,
					entries: [ ]	// Each entry should be an Object with members fraction, bytesLoaded, bytesTotal and docElem. 
				};
				
			calculateLoadProgress ( progress );
			
			if ( progress.entries.length > 0 ) 
			{
				progress.fraction = 0;
				var totalWeight : Number = 0;
				
				// Sanity test to overcome rounding errors.
				//
				var fullyLoaded : Boolean = true;
			
				for ( i = 0; i < progress.entries.length; i++ )
				{
					var entry : Object = progress.entries [ i ];
					if ( entry.fraction == 0 || entry.bytesLoaded != entry.bytesTotal ) fullyLoaded = false; 
					progress.bytesLoaded += entry.bytesLoaded;
					progress.bytesTotal += entry.bytesTotal;
					var weight : Number = entry.docElem.getStyle ( "progress-weight" );
					totalWeight += weight;
					progress.fraction += weight * entry.fraction;
				}
				
				if ( totalWeight > 0 ) progress.fraction /= totalWeight;
				
				if ( fullyLoaded ) progress.fraction = 1;
				
				if ( addListeners )
				{
					// Add listeners to those DocumentElements that have loadable 
					// content.
					//
					for ( var i : uint = 0; i < progress.entries.length; i++ )
					{
						addListener ( progress.entries [ i ].docElem, FrontalEvent.PROGRESS, progressHandler )
					}
				}
			}
			
			// Do some sanitizing.
			//
			if ( progress.fraction > 1 ) progress.fraction = 1;
			if ( progress.bytesLoaded > progress.bytesTotal ) progress.bytesLoaded = progress.bytesTotal;
			return progress;
		}
		
		/*
			Function: progressHandler
			
				This method handles events sent by any descendants with dynamic
				content. By default this method does nothing. Containers with 
				progress indicators will override it to run the onProgress
				interaction.
		*/
		/**
		 * @private
		 */
		internal function progressHandler ( event : FrontalEvent ) : void { }
		
		/*
			Function: calculateLoadProgress
			
				Add this particular DocumentElement's dynamic content statistics
				to the cumulative result stored in the passed-in progress
				Object.
				
				The progress Object has members bytesLoaded, bytesTotal and
				loadableElements. Only add values to bytesLoaded and bytesTotal
				as they are the sum of the results of any number of
				DocumentElements. Only add this DocumentElement to 
				loadableElements if it has loadable content.
			
			Parameters:
			
				progress	- The progress statistics calculated so far. 
		*/
		/**
		 * @private
		 */
		public function calculateLoadProgress ( progress : Object ) : void 
		{
			if ( hasManager && childrenManager.getStyle ( "hide-unselected" ) && ! getStyle ( "progress-includes-all-assets" ) )
			{
				// In this case, we only get the progress of the initial
				// or current Container.
				//
				var container : DocumentElement = childrenManager.next;
				if ( container == null ) container = childrenManager.current;
				if ( container == null ) container = childrenManager.getInitial ( );
				if ( container != null ) container.calculateLoadProgress ( progress );
			}
			else
			{
				for ( var i : uint = 0; i < elements.length; i++ ) elements [ i ].calculateLoadProgress ( progress );
			}
		}
		
		public function toXMLString ( options : Object = null ) : String 
		{
			var result : String = "";

			if ( options == null ) options = { };
			
			if ( attributeExists ( "altId" ) )
			{
				// Process alternative content attribute.
				//
				var alt : DocElemAlt = getElementById ( getAttribute ( "altId" ) ) as DocElemAlt;
				if ( alt != null )
				{
					options.includeAlt = true;
					result = alt.toXMLString ( options );
					options.includeAlt = false;
				}
				return result;
			}
			
			var name : String = node.localName ( );
			var ns : Namespace = node.namespace ( );
			var namespaces : String = "";
			var prefixesSeen : Object = { };
			
			for ( var i : uint = 0; i < node.namespaceDeclarations ( ).length; i++ ) 
			{
				var thisNS : Namespace = node.namespaceDeclarations ( ) [ i ];
				if ( ! prefixesSeen.hasOwnProperty ( thisNS.prefix ) )
				{
					prefixesSeen [ thisNS.prefix ] = true;
					namespaces += " xmlns" + ( thisNS.prefix == "" ? "" : ":" + thisNS.prefix ) + "=\"" + Util.htmlEscape ( thisNS.uri ) + "\"";
				}
				if ( ns != null && ns.uri == thisNS.uri && ns.prefix != "" ) name = ns.prefix + ":" + name;
			}
			
			result = "<" + name + namespaces;
			
			for each ( var namespace : String in attributes.getNamespaces ( ) )
			{
				for each ( var attribute : String in attributes.getAttributes ( namespace ) ) 
				{
					if ( attribute == "style" && namespace == "" ) continue;
					var value : String = getAttribute ( attribute, namespace );
					
					if ( options.isSEOProcess && attribute == "src" )
					{
						// XXX It would be best to let each DocumentElement subclass do
						// some of its own processing but tearing this beast apart will
						// take some planning.
						//
						value = AssetManager.getInstance ( ).getFullURL ( value, getStyle ( "base-url" ) );
					}
					
					var nsPrefix : String = "";
					
					if ( namespace != "" )
					{
						var inScopeNamespaces : Array = node.inScopeNamespaces ( );
						for ( i = 0; i < inScopeNamespaces.length; i++ )
						{
							if ( inScopeNamespaces [ i ].uri == namespace )
							{
								nsPrefix = inScopeNamespaces [ i ].prefix + ":";
								break;
							}
						}
					}
					
					result += " " + nsPrefix + attribute + "=\"" + Util.htmlEscape ( value ) + "\"";
				}
			}
			
			// Add special attributes to support the SEO process.
			//
			if ( options.isSEOProcess )
			{
				if ( isManaged && parent.childrenManager.getStyle ( "deep-link" ) == "path" )
				{
					// Let the SEO process know this element's deep-link path.
					//
					result += " frDeepLinkPath=\"" + Util.htmlEscape ( parent.childrenManager.getDeepLinkIndex ( this ) ) + "\"";
				}
			
				if ( getStyle ( "is-link" ) && attributeExists ( "mgrId" ) )
				{
					// If this is a link to a deep-linked element then include the
					// info needed to build the URL here.
					//
					var mgr : ContainerManager = getElementById ( getAttribute ( "mgrId" ) ) as ContainerManager;
					
					if ( mgr != null )
					{
						var deepLink : String;
						var element : Container;
						
						if ( attributeExists ( "elemNdx" ) )
						{
							element = mgr.getContainerByIndex ( getAttribute ( "elemNdx" ) );
						}
						else if ( ! isNaN ( getStyle ( "jump-delta" ) ) )
						{
							// Assume it's a next or previous button.
							//
							element = mgr.getNext ( getStyle ( "jump-delta" ), ! getStyle ( "stop-at-end" ) );
						}
						
						if ( element != null ) deepLink = mgr.getDeepLinkIndex ( element );
						
						if ( deepLink != null ) result += " frDeepLinkType=\"" + mgr.getStyle ( "deep-link" ) + "\" frDeepLinkIndex=\"" + Util.htmlEscape ( deepLink ) + "\"";
					}
				}
			}
			
			var styleAttrResult : String = "";
			
			for each ( var style : Object in attributes.styles.styles )
			{
				if ( style.value != undefined ) styleAttrResult += style.styleDefn.style + ": " + Styles.unconvertValue ( style.styleDefn, style.value ) + "; ";
			}
			
			if ( styleAttrResult.length > 0 ) result += " style=\"" + Util.htmlEscape ( styleAttrResult ) + "\"";
			
			if ( processesOwnContent || elements.length > 0 )
			{
				result += ">";
				if ( processesOwnContent )
				{
					result += node.children ( ).toXMLString ( );
				}
				else
				{
					result += "\n";
					for ( i = 0; i < elements.length; i++ ) result += elements [ i ].toXMLString ( options );
				}
				result += "</" + name + ">\n";
			}
			else
			{
				result += " />\n";
			}
			
			return result;
		}
	}
}

